/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package net.huangp.service;

import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.huangp.dao.CSVDAO;
import net.huangp.dao.ErrorLogDAO;
import net.huangp.dao.SMSDAO;
import net.huangp.dao.TransactionDAO;
import net.huangp.domain.ErrorLog;
import net.huangp.domain.SMS;
import net.huangp.domain.Transaction;
import net.huangp.framework.ThreadContext;
import net.huangp.jms.JmsProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

/**
 *
 * @author Patrick Huang
 */
@Service
@Transactional
public class SMSServiceImpl implements SMSService{
    //--------------------------------------------------------------------------
    // Static Properties
    //--------------------------------------------------------------------------
    private static final Logger logger = LoggerFactory.getLogger(SMSServiceImpl.class);
    //--------------------------------------------------------------------------
    // Properties
    //--------------------------------------------------------------------------
    private SMSDAO smsDao;
    private CSVDAO csvDao;
    private TransactionDAO tranDao;
    private JmsProducer jmsProducer;

    @Autowired
    private ErrorLogDAO errorLogDAO;
    // -------------------------------------------------------------------------
    // Implementation Methods
    // -------------------------------------------------------------------------
    /**
     * Constructor injection SMSDAO
     * @param smsDao
     */
    @Autowired
    public SMSServiceImpl(SMSDAO smsDao,CSVDAO csvDao,TransactionDAO tranDao,JmsProducer jmsProducer){
        this.smsDao = smsDao;
        this.csvDao = csvDao;
        this.tranDao = tranDao;
        this.jmsProducer = jmsProducer;
    }

    /**
     * get sms by id
     * @param id
     * @return
     */
    @Transactional(readOnly=true)
    public SMS getSMS(Long id) {
        return smsDao.getSMS(id);
    }

    /**
     * save or update sms
     * @param sms
     */
    public void saveOrUpdateSMS(SMS sms) {
        smsDao.saveOrUpdateSMS(sms);
    }

    /**
     * delete sms by id
     * @param id
     */
    public void deleteSMS(Long id) {
        smsDao.deleteSMS(id);
    }

    /**
     * search sms by given example.
     * Empty string in example will be set to null so that when hibernate
     * generates query, it will omit these fields.
     * @param example
     * @return
     */
    @Transactional(readOnly=true)
    public List<SMS> searchSMSByExample(SMS example) {
        BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(example);
        String propName = null;
        String propValue = null;
        for(PropertyDescriptor pd : beanWrapper.getPropertyDescriptors()){
            if(pd.getPropertyType().equals(String.class)){
                propName = pd.getName();
                if(!beanWrapper.isWritableProperty(propName)){
                    continue;
                }
                propValue = (String) beanWrapper.getPropertyValue(propName);
                if(!StringUtils.hasText(propValue) && propValue != null){
                    logger.debug("setting empty property [{}] to null",propName);
                    beanWrapper.setPropertyValue(propName, null);
                }
            }
        }
        return smsDao.searchSMSByExample(example);
    }

    /**
     * parse a csv file into a list of sms object
     * @param file
     * @return
     */
    public List<SMS> parseCSV(File file) {
        List<SMS> result = csvDao.parseCSV(file);
        //we need to persist all errored records if applicable
        if(!ThreadContext.getThreadLocalMap().isEmpty()){
            logger.warn("has parsing errors.Check error log.");
            for(Serializable error : ThreadContext.getThreadLocalMap().values()){
                if(error instanceof ErrorLog){
                    this.errorLogDAO.insertErrorLog((ErrorLog) error);                 
                }
            }
        }
        return result;
    }

    /**
     * list sms with pagination
     * @param start
     * @param max
     * @return
     */
    public List<SMS> listSMS(int start, int max) {
        return smsDao.listSMS(start, max);
    }

    /**
     * count how many sms in database
     * @return
     */
    public Long countSMS(){
        return smsDao.countSMS();
    }

    /**
     * bulk save a list of sms
     * @param smsList
     * @return number of sms persisted
     */
    public int bulkSaveSMS(List<SMS> smsList) {
        int count = 0;
        for(SMS sms : smsList){
            try{
                saveOrUpdateSMS(sms);
                count++;
            }
            catch(Exception e){
                logger.error("exception in persisting sms",e);
                logger.error("error sms:{}",sms);
                //store errored lines into somewhere
                ErrorLog log = new ErrorLog();
                log.setError(e.getMessage());
                log.setErrorDetail(sms.toDelimiteredString());
                this.errorLogDAO.insertErrorLog(log);
                log = null;
            }
        }
        //the file may no longer needed
        if(count != smsList.size()){
            logger.warn("some lines can not be persisted");   
        }
        logger.info("persisted {} sms",count);
        return count;
    }

    public long backgroundProcessCSV(File file) {
        Transaction tran = this.tranDao.createTransaction(file.getName());
        logger.info("created transaction:{}",tran);
        this.jmsProducer.sendMessage(tran.getId(), file.getAbsolutePath());
        return tran.getId();
    }
    // -------------------------------------------------------------------------
    // Mutator Methods
    // -------------------------------------------------------------------------
    public void setErrorLogDAO(ErrorLogDAO errorLogDAO) {
        this.errorLogDAO = errorLogDAO;
    }
}
